package com.sinog.core.util;

import lombok.extern.slf4j.Slf4j;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;

/**
 * @desc 接口密钥工具类
 * @author lzq
 * @date 2020-09-01 8:07 下午
 */
@Slf4j
public final class Hex {

    /**
     * 用于建立十六进制字符的输出的小写字符数组
     */
    private static final char[] DIGITS_LOWER = {'0','1','2','3','4','5','6','7','8','9','a','b','c','d','e','f'};
    /**
     * 用于建立十六进制字符的输出的大写字符数组
     */
    private static final char[] DIGITS_UPPER = {'0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F'};

    /**
     * 将字节数组转换为十六进制字符数组 * * @param data * byte[] * @param toDigits * 用于控制输出的char[]
     * @param data data
     * @param toDigits toDigits
     * @return char[]
     */
    private static char[] encodeHex(byte[] data,char[] toDigits) {
        int length = data.length;
        char[] out = new char[length << 1];
        for(int i = 0, j = 0; i < length; i++) {
            out[j++] = toDigits[(0xF0 & data[i]) >>> 4];
            out[j++] = toDigits[0x0F & data[i]];
        }
        return out;
    }

    /**
     * 将字节数组转换为十六进制字符串 *
     * @param data data
     * @return String
     */
    private static String encodeHexStr(byte[] data) {
        return encodeHexStr(data,true);
    }

    /**
     * 将字节数组转换为十六进制字符串 *
     * @param data <code>true</code> 传换成小写格式 ， <code>false</code> 传换成大写格式
     * @param toLowerCase toLowerCase
     * @return String
     */
    private static String encodeHexStr(byte[] data,boolean toLowerCase) {
        return encodeHexStr(data,toLowerCase ? DIGITS_LOWER : DIGITS_UPPER);
    }

    /**
     * 将字节数组转换为十六进制字符串
     * @param data 用于控制输出的char[]
     * @param toDigits toDigits
     * @return String
     */
    private static String encodeHexStr(byte[] data,char[] toDigits) {
        return new String(encodeHex(data,toDigits));
    }

    /**
     * 将十六进制字符数组转换为字节数组 *如果源十六进制字符数组是一个奇怪的长度，将抛出运行时异常
     * @param data 十六进制char[] * @return byte[]
     * @return byte[]
     */
    private static byte[] decodeHex(char[] data) {
        int len = data.length;
        if(0 != (len & 0x01)) {
            throw new RuntimeException("Odd number of characters.");
        }
        // two characters form the hex value.
        byte[] out = new byte[len >> 1];
        for(int i = 0, j = 0; j < len; i++) {
            int fstep = toDigit(data[j],j) << 4;
            j++;
            fstep |= toDigit(data[j],j);
            j++;
            out[i] = (byte)(fstep & 0xFF);
        }
        return out;
    }

    /**
     * 将十六进制字符转换成一个整数 * * @param ch * 十六进制char * @param index * 十六进制字符在字符数组中的位置
     * @param ch ch
     * @param index index
     * @return 一个整数 * @throws RuntimeException * 当ch不是一个合法的十六进制字符时，抛出运行时异常
     */
    private static int toDigit(char ch,int index) {
        int digit = Character.digit(ch,16);
        if(-1 == digit) {
            throw new RuntimeException("Illegal hexadecimal character " + ch + " at index " + index);
        }
        return digit;
    }

    /**
     * 描述
     * @param data data
     * @param key key
     * @return byte[]
     */
    private static byte[] hMacSha256(String data,byte[] key) throws java.security.NoSuchAlgorithmException, java.security.InvalidKeyException {
        String algorithm = "HmacSHA256";
        Mac mac = Mac.getInstance(algorithm);
        mac.init(new SecretKeySpec(key,algorithm));
        return mac.doFinal(data.getBytes(StandardCharsets.UTF_8));
    }

    /**
     * 服务端生成，不暴漏给客户端
     * @param uid uid
     * @return String
     */
    public static String genAccessKey(String uid) throws java.security.NoSuchAlgorithmException, java.security.InvalidKeyException {
        return encodeHexStr(hMacSha256(uid,("AWS4" + uid).getBytes(StandardCharsets.UTF_8)));
    }

    /**
     * 客户端 生成验证码
     * @param ak ak
     * @param ts ts
     * @param serviceName serviceName
     * @return String
     */
    public static String genSignature(String ak,String ts,String serviceName) throws java.security.NoSuchAlgorithmException, java.security.InvalidKeyException {
        byte[] kDate = decodeHex(ak.toCharArray());
        byte[] kRegion = hMacSha256(ts,kDate);
        byte[] kService = hMacSha256(serviceName,kRegion);
        byte[] kSigning = hMacSha256("aws4_request",kService);
        return encodeHexStr(kSigning,true);
    }

    /**
     * 服务方 产生验证码
     * @param uuid uuid
     * @param ts ts
     * @param serviceName serviceName
     * @return String
     */
    public static String getSignatureKey(String uuid,String ts,String serviceName) throws java.security.NoSuchAlgorithmException, java.security.InvalidKeyException {
        byte[] kSecret = ("AWS4" + uuid).getBytes(StandardCharsets.UTF_8);
        byte[] kDate = hMacSha256(uuid,kSecret);
        byte[] kRegion = hMacSha256(ts,kDate);
        byte[] kService = hMacSha256(serviceName,kRegion);
        byte[] kSigning = hMacSha256("aws4_request",kService);
        return encodeHexStr(kSigning,true);
    }
}